home *** CD-ROM | disk | FTP | other *** search
- # -*- coding: utf-8 -*-
- """
- """
-
- __author__ = 'Robert Ancell <bob27@users.sourceforge.net>'
- __license__ = 'GNU General Public License Version 2'
- __copyright__ = 'Copyright 2005-2006 Robert Ancell'
-
- __all__ = ['Application']
-
- import sys
- import os
- import errno
- from gettext import gettext as _
- import traceback
- import time
-
- import config
- import ui
- import gtkui
- import game
- import player
- import chess.board
- import chess.lan
- import chess.pgn
- import ai
- import network
- import display
- import history
-
- from defaults import *
-
- class PlayerTimer(ui.TimerFeedback):
- """
- """
-
- def __init__(self, game, colour, duration):
- self.game = game
- self.colour = colour
- self.duration = duration
- self.controller = game.application.ui.controller.addTimer(self, duration)
-
- def onTick(self, t):
- """Called by ui.TimerFeedback"""
- if self.colour is chess.board.WHITE:
- self.game.view.controller.setWhiteTime(self.duration, t)
- else:
- self.game.view.controller.setBlackTime(self.duration, t)
-
- def onExpired(self):
- """Called by ui.TimerFeedback"""
- if self.colour is chess.board.WHITE:
- self.game.getWhite().outOfTime()
- else:
- self.game.getBlack().outOfTime()
-
- class ChessGame(game.ChessGame):
- """
- """
-
- # Mapping between piece names and promotion types
- __promotionMapping = {'queen': chess.board.QUEEN, 'knight': chess.board.KNIGHT, 'bishop': chess.board.BISHOP, 'rook': chess.board.ROOK}
-
- def __init__(self, application, name):
- """Constructor for a chess game.
-
- 'application' is a reference to the glChess application.
- 'name' is the name of the game (string).
- """
- self.application = application
- self.name = name
- self.__aiPlayers = []
-
- # TEMP
- self.duration = 0
- self.wT = None
- self.bT = None
-
- self.fileName = None
- self.inHistory = False
- self.needsSaving = False
-
- # Call parent constructor
- self.view = None
- game.ChessGame.__init__(self)
-
- self.view = display.View(self)
- self.view.controller = application.ui.controller.setView(name, self.view)
- self.view.updateRotation(animate = False)
-
- self.view.showMoveHints(config.get('show_move_hints') is True)
- self.view.showBoardNumbering(config.get('show_numbering') is True)
- self.view.showSmooth(config.get('show_3d_smooth') is True)
-
- # Watch for piece moves with a player
- self.__movePlayer = player.MovePlayer(self)
- self.addSpectator(self.__movePlayer)
-
- self.date = time.strftime('%Y.%m.%d')
-
- def addAIPlayer(self, name, profile, level):
- """Create an AI player.
-
- 'name' is the name of the player to create (string).
- 'profile' is the the AI profile to use (ai.Profile).
- 'level' is the difficulty level to use (string).
-
- Returns an AI player to use (game.ChessPlayer).
- """
- # Translators: Description of an AI player used in log window. %(name)s is replaced with
- # the name of the AI player. %(game)s is replaced with the name of the game the AI player
- # is in.
- description = _("'%(name)s' in '%(game)s'") % {'name': name, 'game': self.name}
- p = player.AIPlayer(self.application, name, profile, level, description)
- self.__aiPlayers.append(p)
- self.application.watchAIPlayer(p)
- return p
-
- def addHumanPlayer(self, name):
- """Create a human player.
-
- 'name' is the name of the player to create.
-
- Returns a human player to use (game.ChessPlayer).
- """
- return player.HumanPlayer(self, name)
-
- def setTimer(self, duration, whiteTime, blackTime):
- self.duration = duration
- if duration <= 0:
- return
-
- self.view.controller.setWhiteTime(duration, whiteTime)
- self.view.controller.setBlackTime(duration, blackTime)
-
- self.wT = PlayerTimer(self, chess.board.WHITE, whiteTime)
- self.bT = PlayerTimer(self, chess.board.BLACK, blackTime)
- self.wT.controller.run()
-
- self.setTimers(self.wT, self.bT)
-
- def getHumanPlayer(self):
- """Get the human player.
-
- Returns the human player (HumanPlayer) or None if no human players.
- If both players are human the current player is returned.
- """
- c = self.getCurrentPlayer()
- if isinstance(c, player.HumanPlayer):
- return c
- white = self.getWhite()
- black = self.getWhite()
- if c is white:
- opponent = black
- else:
- opponent = white
- if isinstance(opponent, player.HumanPlayer):
- return opponent
- return None
-
- def currentPlayerIsHuman(self):
- """Test if the player to move is human.
-
- Returns True if the current player is human and able to move.
- """
- p = self.getCurrentPlayer()
- return isinstance(p, player.HumanPlayer) and p.isReadyToMove()
-
- def squareIsFriendly(self, coord):
- """
- """
- owner = self.getSquareOwner(coord)
- if owner is None:
- return False
- return owner is self.getCurrentPlayer()
-
- def moveHuman(self, start, end):
- """
- """
- assert(self.currentPlayerIsHuman())
- p = self.getCurrentPlayer()
- if p is self.getWhite():
- colour = chess.board.WHITE
- else:
- colour = chess.board.BLACK
-
- # Use configured promotion type
- try:
- promotionType = self.__promotionMapping[config.get('promotion_type')]
- except KeyError:
- promotionType = chess.board.QUEEN
-
- # Make the move
- move = chess.lan.encode(colour, start, end, promotionType = promotionType)
- p.move(move)
-
- # Notify move
- self.view.controller.setAttention(False)
-
- def toPGN(self, pgnGame):
- """Write the properties of this game into a PGN game.
-
- 'pgnGame' is the game to write into (pgn.PGNGame). All the tags should be unset.
- """
- white = self.getWhite()
- black = self.getBlack()
-
- pgnGame.setTag(chess.pgn.TAG_EVENT, self.name)
- pgnGame.setTag(chess.pgn.TAG_WHITE, white.getName())
- pgnGame.setTag(chess.pgn.TAG_BLACK, black.getName())
- pgnGame.setTag(chess.pgn.TAG_DATE, self.date)
-
- results = {game.RESULT_WHITE_WINS: chess.pgn.RESULT_WHITE_WIN,
- game.RESULT_BLACK_WINS: chess.pgn.RESULT_BLACK_WIN,
- game.RESULT_DRAW: chess.pgn.RESULT_DRAW}
- try:
- value = results[self.result]
- except KeyError:
- pass
- else:
- pgnGame.setTag(chess.pgn.TAG_RESULT, value)
-
- rules = {game.RULE_ABANDONMENT: chess.pgn.TERMINATE_ABANDONED,
- game.RULE_TIMEOUT: chess.pgn.TERMINATE_TIME_FORFEIT,
- game.RULE_DEATH: chess.pgn.TERMINATE_DEATH}
- try:
- value = rules[self.rule]
- except KeyError:
- pass
- else:
- pgnGame.setTag(chess.pgn.TAG_TERMINATION, value)
-
- if self.duration > 0:
- pgnGame.setTag(chess.pgn.TAG_TIME_CONTROL, str(self.duration))
- if self.wT is not None:
- pgnGame.setTag('WhiteTime', str(self.wT.controller.getRemaining()))
- if self.bT is not None:
- pgnGame.setTag('BlackTime', str(self.bT.controller.getRemaining()))
-
- # FIXME: AI levels
- if isinstance(white, ai.Player):
- (profile, level) = white.getProfile()
- pgnGame.setTag('WhiteAI', profile)
- pgnGame.setTag('WhiteLevel', level)
- if isinstance(black, ai.Player):
- (profile, level) = black.getProfile()
- pgnGame.setTag('BlackAI', profile)
- pgnGame.setTag('BlackLevel', level)
-
- moves = self.getMoves()
- for m in moves:
- pgnMove = chess.pgn.PGNMove()
- pgnMove.move = m.sanMove
- pgnMove.nag = m.nag
- pgnMove.comment = m.comment
- pgnGame.addMove(pgnMove)
-
- def animate(self, timeStep):
- """
- """
- return self.view.scene.controller.animate(timeStep)
-
- def endMove(self, p):
- game.ChessGame.endMove(self, p)
- self.view.updateRotation()
-
- def remove(self):
- """Remove this game"""
- # Remove AI player windows
- for p in self.__aiPlayers:
- p.window.close()
- self.application.unwatchAIPlayer(p)
-
- # Stop the game
- self.abort()
-
- # Remove the game from the UI
- self.application._removeGame(self)
- self.view.controller.close()
-
- def setNeedsSaving(self, needsSaving):
- """
- """
- # Autosaved games don't need saving
- if self.inHistory:
- needsSaving = False
-
- if self.needsSaving == needsSaving:
- return
- self.needsSaving = needsSaving
- self.view.controller.setNeedsSaving(needsSaving)
-
- def save(self):
- """Save this game"""
- pgnGame = chess.pgn.PGNGame()
- self.toPGN(pgnGame)
- if self.inHistory:
- # Don't bother if haven't made any significant moves
- if len(self.getMoves()) < 2:
- return
- self.application.history.save(pgnGame, self.fileName)
- else:
- try:
- f = file(self.fileName, 'w')
- lines = pgnGame.getLines()
- for line in lines:
- f.write(line + '\n')
- f.write('\n')
- f.close()
- except IOError, e:
- return e.strerror
-
- self.setNeedsSaving(False)
- self.application.logger.addLine('Saved game %s to %s' % (repr(self.name), self.fileName))
-
- class UI(ui.UIFeedback):
- """
- """
-
- def __init__(self, application):
- """
- """
- self.controller = gtkui.GtkUI(self)
- self.application = application
-
- self.splashscreen = display.Splashscreen(self)
- self.splashscreen.controller = self.controller.setView('', self.splashscreen, isPlayable = False)
-
- self.ggzConfig = network.GGZConfig()
- dialog = network.GGZNetworkDialog(self)
- self.networkDialog = dialog.controller = self.controller.addNetworkDialog(dialog)
- for server in self.ggzConfig.getServers():
- dialog.controller.addProfile(server, server.name)
-
- def onAnimate(self, timeStep):
- """Called by ui.UIFeedback"""
- return self.application.animate(timeStep)
-
- def onReadFileDescriptor(self, fd):
- """Called by ui.UIFeedback"""
- try:
- handler = self.application.ioHandlers[fd]
- except KeyError:
- return False
- else:
- result = handler.read()
- if result is False:
- self.application.ioHandlers.pop(fd)
- return result
-
- def onWriteFileDescriptor(self, fd):
- """Called by ui.UIFeedback"""
- try:
- handler = self.application.ioHandlers[fd]
- except KeyError:
- return False
- else:
- result = handler.write()
- if result is False:
- self.application.ioHandlers.pop(fd)
- return result
-
- def onGameStart(self, game):
- """Called by ui.UIFeedback"""
- if game.white.type == '':
- w = None
- else:
- w = (game.white.type, game.white.level)
- if game.black.type == '':
- b = None
- else:
- b = (game.black.type, game.black.level)
- g = self.application.addLocalGame(game.name, game.white.name, w, game.black.name, b)
- if g is None:
- return
- g.inHistory = True
- self.application.logger.addLine('Starting game %s between %s (%s) and %s (%s). (%i moves)' % \
- (game.name,
- game.white.name, str(game.white.type),
- game.black.name, str(game.black.type), len(game.moves)))
-
- g.setTimer(game.duration, game.duration, game.duration)
- g.start(game.moves)
-
- def loadGame(self, path, configure):
- """Called by ui.UI"""
- try:
- p = chess.pgn.PGN(path, 1)
- except chess.pgn.Error, e:
- return str(e)
- except IOError, e:
- return e.strerror
-
- # Use the first game
- self.application.addPGNGame(p[0], path, configure)
-
- return None
-
- def onNewNetworkGame(self):
- """Called by ui.UIFeedback"""
- self.networkDialog.setVisible(True)
-
- def onQuit(self):
- """Called by ui.UIFeedback"""
- self.application.quit()
-
- class Application:
- """
- """
-
- def __init__(self):
- """Constructor for glChess application"""
- self.__aiProfiles = {} # The AI types
- self.ioHandlers = {} # Objects with IO keyed by file descriptor
- self.networkConnections = {} # Network connections keyed by file descriptor
- self.__game = None # The game in progress
-
- self.ui = UI(self)
-
- self.history = history.GameHistory()
-
- # Translators: Name of the log that displays application events
- title = _('Application Log')
- self.logger = self.ui.controller.addLogWindow(title, '', '')
-
- def addAIProfile(self, profile):
- """Add a new AI profile into glChess.
-
- 'profile' is the profile to add (ai.Profile).
- """
- name = profile.name
- assert(self.__aiProfiles.has_key(name) is False)
- self.__aiProfiles[name] = profile
- self.ui.controller.addAIEngine(name)
-
- def getAIProfile(self, name):
- """Get an installed AI profile.
-
- 'name' is the name of the profile to get (string).
-
- Return the profile (ai.Profile) or None if it does not exist.
- """
- try:
- return self.__aiProfiles[name]
- except KeyError:
- return None
-
- def watchAIPlayer(self, p):
- """
- """
- fd = p.fileno()
- if fd is not None:
- self.ioHandlers[fd] = p
- self.ui.controller.watchFileDescriptor(fd)
-
- def unwatchAIPlayer(self, p):
- """
- """
- fd = p.fileno()
- if fd is not None:
- self.ioHandlers.pop(fd)
-
- def addGame(self, name):
- if self.__game is not None:
- # Save the current game to the history
- if self.__game.inHistory:
- response = ui.SAVE_YES
- elif self.__game.needsSaving:
- response = self.ui.controller.requestSave('Save current game?')
- else:
- response = ui.SAVE_NO
-
- if response is ui.SAVE_YES:
- self.__game.save()
- elif response is ui.SAVE_ABORT:
- return None
- self.__game = ChessGame(self, name)
- return self.__game
-
- def addLocalGame(self, name, whiteName, whiteType, blackName, blackType):
- """Add a chess game into glChess.
-
- 'name' is the name of the game (string).
- 'whiteName' is the name of the white player (string).
- 'whiteType' is a 2-tuple containing the AI profile name and difficulty level (str, str) or None for human players.
- 'blackName' is the name of the black player (string).
- 'blackType' is a 2-tuple containing the AI profile name and difficulty level (str, str) or None for human players.
-
- Returns the game object. Use game.start() to start the game.
- """
- # FIXME: Replace arguments with player objects
-
- # Create the game
- g = self.addGame(name)
- if g is None:
- return None
-
- msg = ''
- if whiteType is None:
- p = g.addHumanPlayer(whiteName)
- else:
- (profile, level) = whiteType
- p = g.addAIPlayer(whiteName, self.__aiProfiles[profile], level)
- g.setWhite(p)
-
- if blackType is None:
- p = g.addHumanPlayer(blackName)
- else:
- (profile, level) = blackType
- p = g.addAIPlayer(blackName, self.__aiProfiles[profile], level)
- g.setBlack(p)
-
- return g
-
- def addPGNGame(self, pgnGame, path, configure = False):
- """Add a PGN game.
-
- 'pgnGame' is the game to add (chess.pgn.PGNGame).
- 'path' is the path this game was loaded from (string or None).
-
- Returns the game object. Use game.start() to start the game.
- """
- gameProperties = ui.Game()
-
- gameProperties.path = path
- gameProperties.name = pgnGame.getTag(chess.pgn.TAG_EVENT)
- gameProperties.white.name = pgnGame.getTag(chess.pgn.TAG_WHITE)
- gameProperties.black.name = pgnGame.getTag(chess.pgn.TAG_BLACK)
- moves = []
- for pgnMove in pgnGame.getMoves():
- moves.append(pgnMove.move)
- gameProperties.moves = moves
-
- missingEngines = False
- gameProperties.white.type = pgnGame.getTag('WhiteAI', '')
- if gameProperties.white.type == '':
- w = None
- else:
- if not self.__aiProfiles.has_key(gameProperties.white.type):
- missingEngines = True
- gameProperties.white.level = pgnGame.getTag('WhiteLevel')
- if gameProperties.white.level is None:
- gameProperties.white.level = 'normal'
- w = (gameProperties.white.type, gameProperties.white.level)
-
- gameProperties.black.type = pgnGame.getTag('BlackAI', '')
- if gameProperties.black.type == '':
- b = None
- else:
- if not self.__aiProfiles.has_key(gameProperties.black.type):
- missingEngines = True
- gameProperties.black.level = pgnGame.getTag('BlackLevel')
- if gameProperties.black.level is None:
- gameProperties.black.level = 'normal'
- b = (gameProperties.black.type, gameProperties.black.level)
-
- # If some of the properties were invalid display the new game dialog
- if missingEngines or configure:
- self.ui.controller.reportGameLoaded(gameProperties)
- return
-
- newGame = self.addLocalGame(gameProperties.name, gameProperties.white.name, w, gameProperties.black.name, b)
- if newGame is None:
- return None
- newGame.date = pgnGame.getTag(chess.pgn.TAG_DATE)
- newGame.fileName = path
- if gameProperties.moves:
- newGame.start(gameProperties.moves)
- else:
- newGame.start()
-
- # Comment on each move
- # FIXME: This should be done through a method so the UI can update better
- moves = newGame.getMoves()
- pgnMoves = pgnGame.getMoves()
- for i in xrange(len(moves)):
- moves[i].comment = pgnMoves[i].comment
- moves[i].nag = pgnMoves[i].nag
-
- # Get the last player to resign if the file specifies it
- result = pgnGame.getTag(chess.pgn.TAG_RESULT, None)
- loser = None
- if result == chess.pgn.RESULT_DRAW:
- newGame.claimDraw()
- if newGame.result != game.RESULT_DRAW:
- newGame.endGame(game.RESULT_DRAW, game.RULE_AGREEMENT)
- elif result == chess.pgn.RESULT_INCOMPLETE:
- if newGame.result != game.RESULT_IN_PROGRESS:
- print "WARNING: PGN file specifies game in progress, glChess does't..."
- elif result == chess.pgn.RESULT_WHITE_WIN:
- loser = newGame.getBlack()
- elif result == chess.pgn.RESULT_BLACK_WIN:
- loser = newGame.getWhite()
- if newGame.result == game.RESULT_IN_PROGRESS and loser is not None:
- loser.resign()
-
- duration = 0
- value = pgnGame.getTag(chess.pgn.TAG_TIME_CONTROL)
- if value is not None:
- timers = value.split(':')
- try:
- duration = int(timers[0])
- except ValueError:
- print 'Unknown time control: ' + value
-
- value = pgnGame.getTag('WhiteTime', duration * 1000)
- try:
- whiteTime = int(value)
- except ValueError:
- whiteTime = duration
- value = pgnGame.getTag('BlackTime', duration * 1000)
- try:
- blackTime = int(value)
- except ValuError:
- blackTime = duration
- newGame.setTimer(duration, whiteTime / 1000, blackTime / 1000)
-
- # No need to save freshly loaded game
- newGame.setNeedsSaving(False)
-
- return newGame
-
- def start(self):
- """Run glChess.
-
- This method does not return.
- """
- self.logger.addLine('This is glChess %s' % VERSION)
-
- # Load AI profiles
- profiles = ai.loadProfiles()
-
- for p in profiles:
- p.detect()
- if p.path is not None:
- self.logger.addLine('Detected AI: %s at %s' % (p.name, p.path))
- self.addAIProfile(p)
-
- nArgs = len(sys.argv)
-
- # Load existing games
- if nArgs == 1:
- self.__autoload()
-
- # Load requested game
- elif nArgs == 2:
- path = sys.argv[1]
- import time
- self.logger.addLine('loading...')
- s = time.time()
- try:
- p = chess.pgn.PGN(path, 1)
- except chess.pgn.Error, e:
- # TODO: Pop-up dialog
- self.logger.addLine('Unable to open PGN file %s: %s' % (path, str(e)))
- except IOError, e:
- self.logger.addLine('Unable to open PGN file %s: %s' % (path, str(e)))
- else:
- # Use the first game
- if len(p) > 0:
- g = self.addPGNGame(p[0], path)
- self.logger.addLine('loaded in %f seconds' % (time.time() - s))
-
- else:
- # FIXME: Should be in a dialog
- # Translators: Text displayed on the command-line if an unknown argument is passed
- print _('Usage: %s [game]') % sys.argv[0]
- sys.exit(0)
-
- # Start default game if no game present
- if self.__game is None and len(self.__aiProfiles) > 0:
- # Try the previously used AIs first, then any other AI on easy mode
- possibleAIs = [(config.get('new_game_dialog/black/type'),
- config.get('new_game_dialog/black/difficulty')),
- (config.get('new_game_dialog/white/type'),
- config.get('new_game_dialog/white/difficulty'))]
- for p in profiles:
- possibleAIs.append((p.name, 'easy'))
-
- for (name, difficulty) in possibleAIs:
- if self.__aiProfiles.has_key(name):
- black = (name, difficulty)
- break
- # Translators: Name of a human versus AI game. The %s is replaced with the name of the AI player
- gameName = _('Human versus %s') % black[0]
- # Translators: Name of white player in a default game
- whiteName = _('White')
- # Translators: Name of black player in a default game
- blackName = _('Black')
- g = self.addLocalGame(gameName, whiteName, None, blackName, black)
- g.inHistory = True
- g.start()
-
- # Start UI (does not return)
- try:
- self.ui.controller.run()
- except:
- # FIXME: Isn't this done by bug-buddy?
- print _("""glChess has crashed. Please report this bug to http://bugzilla.gnome.org
- Debug output:""")
- print traceback.format_exc()
- self.quit()
- sys.exit(1)
-
- def animate(self, timeStep):
- """
- """
- return self.__game.animate(timeStep)
-
- def quit(self):
- """Quit glChess"""
- if self.__game is not None:
- if self.__game.inHistory:
- response = ui.SAVE_YES
- elif self.__game.needsSaving:
- response = self.ui.controller.requestSave(_('Save game before closing?'))
- else:
- response = ui.SAVE_NO
-
- if response == ui.SAVE_YES:
- self.__game.save()
- elif response == ui.SAVE_ABORT:
- return
-
- # Abort current game (will delete AIs etc)
- self.__game.abort()
-
- # Notify the UI
- self.ui.controller.close()
-
- # Exit the application
- sys.exit()
-
- # Private methods
-
- def __autoload(self):
- """Restore games from the autosave file"""
- (pgnGame, fileName, inHistory) = self.history.getUnfinishedGame()
- if pgnGame is not None:
- g = self.addPGNGame(pgnGame, fileName)
- if g is not None:
- g.inHistory = inHistory
-
- if __name__ == '__main__':
- app = Application()
- app.start()
-